Spring MVC 学习笔记
Spring MVC简介
Spring MVC
是Spring框架中用于Web应用快速开发的一个模块,其中的MVC
是Model-View-Controller
的缩写。它是一个广泛应用于图形化用户交互开发中的设计模式,不仅常见于Web开发,也广泛应用于如Swing
和JavaFX
等桌面开发。作为当今业界最主流的Web开发框架,Spring MVC已经成为当前最热门的开发技能。
IDEA环境配置
https://www.cnblogs.com/zuti666/p/13987082.html
核心概念
-
IoC(Inversion of Control)控制反转
- 使用对象时,由主动new产生对象转换为由
外部
提供对象,此过程中对象创建控制权由程序转移到外部
,此思想称为控制反转
- 使用对象时,由主动new产生对象转换为由
-
Spring技术对IoC思想进行了实现
- Spring提供了一个容器,成为IoC容器,用来充当Ioc思想中的
外部
- IoC容器负责对象的创建、初始化等一系列工作,被创建或被管理的对象在IoC容器中统称为Bean
- Spring提供了一个容器,成为IoC容器,用来充当Ioc思想中的
- DI(Dependency Injection)依赖注入
- 当需要使用service对象的时候,同样需要用到dao对象,所以在IoC容器中做了一个依赖绑定。
- 在容器中建立bean与bean之间的依赖关系的整个过程,成为
依赖注入
- 目标:
充分解耦
- 使用IoC容器管理 bean(IoC)
- 在IoC容器内将有依赖关系的bean进行关系绑定(DI)
- 最终效果
- 使用对象时不仅可以直接从IoC容器中获取,并且获取到的bean已经绑定了所有的依赖关系
IoC入门案例
IoC入门案例思路分析
- 管理什么?
(Service与Dao)
- 如何将被管理的对象告知IoC容器?
(配置)
- 被管理的对象交给IoC容器,如何获取到IoC容器?
(接口)
- IoC容器得到后,如何从容器中获取bean?
(接口方法)
- 使用Spring导入哪些坐标?(pom.xml)
案例1
BookDaoImpl
public class BookDaoImpl implements BookDao {
public void save(){
System.out.println("book dao save ...");
}
}
BookServiceImpl
public class BookServiceImpl implements BookService {
private BookDao bookDao = new BookDaoImpl();
public void save(){
System.out.println("book service save ...");
bookDao.save();
}
}
BookDao接口
public interface BookDao {
void save();
}
BookService接口
public interface BookService {
void save();
}
App1
public class app {
public static void main(String[] args) {
BookService bookService = new BookServiceImpl();
bookService.save();
}
}
/*运行结果
book service save ...
book dao save ...
*/
案例2
配置Spring
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<!-- 1.导入spring的坐标spring-context-->
<!-- 2、配置bean-->
<!-- bean标签表示配置bean
id属性表述给bean起名字
class属性表示给bean定义类型-->
<bean id="bookDao" class="com.le1a.dao.impl.BookDaoImpl"/>
<bean id="bookService" class="com.le1a.service.impl.BookServiceImpl"/>
</beans>
app2
package com.le1a;
import com.le1a.dao.BookDao;
import com.le1a.service.BookService;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class app2 {
public static void main(String[] args) {
//3、获取IoC容器
ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
//4、获取bean
// BookDao bookDao = (BookDao) ctx.getBean("bookDap");
// bookDao.save();
//因为IoC容器对service和bean做了绑定,所以无需自己获取bean
BookService bookService = (BookService) ctx.getBean("bookService");
bookService.save();
}
}
总结
1、导入Spring坐标
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>${spring.version}</version>
</dependency>
2、定义Spring管理的类(接口)
public interface BookService{
public void save();
}
public class BookServiceImpl implements BookService {
private BookDao bookDao = new BookDaoImpl();
public void save(){
bookDao.save();
}
}
3、创建Spring配置文件,配置对应类作为Spring管理的bean
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="bookService" class="com.le1a.service.impl.BookServiceImpl"/>
</beans>
注意!bean定义时id属性在同一个上下文中不能重复
4、初始化IoC容器(Spring核心容器/Spring容器),通过容器获取bean
public class app2 {
public static void main(String[] args) {
//3、获取IoC容器
ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
//获取资源
BookService bookService = (BookService) ctx.getBean("bookService");
bookService.save();
}
}
DI入门案例
DI入门案例思路分析
- 基于IoC管理bean
- service中使用new形式创建Dao对象是否保留?
(否)
- Service中需要的Dao对象如何进入到Service中?
(提供方法)
- Service与Dao间的关系如何描述?
(配置)
案例实现
修改BookServiceImpl
public class BookServiceImpl implements BookService {
//5、删除业务层逻辑中使用new的方式创建的dao对象
//private BookDao bookDao = new BookDaoImpl();
private BookDao bookDao;
public void save() {
System.out.println("book service save ...");
bookDao.save();
}
//6、提供对应的set方法
public void setBookDao(BookDao bookDao) {
this.bookDao = bookDao;
}
}
Spring配置文件中添加server与dao的关系
<bean id="bookDao" class="com.le1a.dao.impl.BookDaoImpl"/>
<bean id="bookService" class="com.le1a.service.impl.BookServiceImpl">
<!-- 配置service与dao的关系
property标签表示配置当前bean的属性
name属性表示配置哪一个具体的属性
ref属性表示参照哪一个bean-->
<property name="bookDao" ref="bookDao"/>
</bean>
app2运行结果
如果注释掉property
标签,则会抛出NullPointerException
异常
总结
1、删除使用new的形式创建对象的代码
public class BookServiceImpl implements BookService {
//5、删除业务层逻辑中使用new的方式创建的dao对象
//private BookDao bookDao = new BookDaoImpl();
private BookDao bookDao;
public void save() {
bookDao.save();
}
}
2、提供依赖对象对应的setter方法
public class BookServiceImpl implements BookService {
private BookDao bookDao;
public void save() {
bookDao.save();
}
//6、提供对应的set方法
public void setBookDao(BookDao bookDao) {
this.bookDao = bookDao;
}
}
3、配置service与dao之间的关系
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:util="http://www.springframework.org/schema/util"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
<bean id="bookDao" class="com.le1a.dao.impl.BookDaoImpl"/>
<bean id="bookService" class="com.le1a.service.impl.BookServiceImpl">
<property name="bookDao" ref="bookDao"/>
</bean>
</beans>
ref的bookDao指的是当前容器对应的bean的名称(也就是这里的id),name的bookDao是现在的属性的名称。
Bean基础配置
Bean别名配置
修改Spring配置文件
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd ">
<bean id="bookDao" name="dao" class="com.le1a.dao.impl.BookDaoImpl"/>
<!-- 新增name属性-->
<bean id="bookService" name="service service2" class="com.le1a.service.impl.BookServiceImpl">
<!-- 新增name属性-->
<property name="bookDao" ref="dao"/>
</bean>
</beans>
但是,当如果使用了未被定义的别名时,将会抛出异常,例如这里使用了service4,但是我们并没有定义这个别名
Bean作用范围
public class AppForScope {
public static void main(String[] args) {
ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
BookDao bookDao1 = (BookDao) ctx.getBean("bookDao");
BookDao bookDao2 = (BookDao) ctx.getBean("bookDao");
System.out.println(bookDao1);
System.out.println(bookDao2);
}
}
通过运行结果可以发现,Spring给我们创建的是一个单例Bean,两个bookDao是同一个地址
如果我们想要创建非单例的bean呢?
通过配置来完成,在Spring的配置文件中的bookDao配置中,再添加一个属性scope
,一共有两个属性,分别是singleton
和prototype
,默认的是singleton
,如果换成prototype
,Spring创建的Bean就会是两个不同的地址
-
为什么bean默认为单例呢?
- 对于Spring来说,它帮我们管理的bean要放入IoC容器中,如果创建出的bean不是单例的,那么bean的数量将会是无数个,用一次造一个,用一次造一个,所以使用单例bean会提高容器的效率。
-
适合交给容器进行管理的bean
- 表现层对象
- 业务层对象
- 数据从对象
- 工具对象
-
不适合交给容器进行管理的对象
- 封装实体的域对象
Bean实例化
构造方法(常用)
bean实例化
- bean本质上就是对象,创建bean使用构造方法完成
配置文件
<!--方式一:使用构造方法实例化bean-->
<!-- <bean id="bookDao" name="dao" class="com.le1a.dao.impl.BookDaoImpl" scope="prototype"/>-->
AppForInstanceBook
public class AppForInstanceBook {
public static void main(String[] args) {
ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
BookDao bookDao =(BookDao) ctx.getBean("bookDao");
bookDao.save();
}
}
BookDaoImpl
public class BookDaoImpl implements BookDao {
public BookDaoImpl(){
System.out.println("book dao constructor is running ...");
}
public void save(){
System.out.println("book dao save ...");
}
}
说明了造对象都会调用无参构造方法来构造,无论构造方法是public还是private属性。
静态工厂(了解)
配置文件
<!-- 方式二:使用静态工厂实例化bean-->
<bean id="orderDao" class="com.le1a.factory.OrderDaoFactory" factory-method="getOrderDao"/>
静态工厂
public class OrderDaoFactory {
public static OrderDao getOrderDao(){
return new OrderDaoImpl();
}
}
AppForInstanceOrder
public class AppForInstanceOrder {
public static void main(String[] args) {
// 通过静态工厂创建对象
// OrderDao orderDao = OrderDaoFactory.getOrderDao();
// orderDao.save();
ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
OrderDao orderDao = (OrderDao) ctx.getBean("orderDao");
orderDao.save();
}
}
实例工厂(了解)与FactoryBean(实用)
配置文件
<!-- 方式三: 使用实例工厂实例化bean-->
<bean id="userFactory" class="com.le1a.factory.UserDaoFactory"/>
<bean id="userDao" factory-bean="userFactory" factory-method="getUserDao"/>
实例工厂
public class UserDaoFactory {
public UserDao getUserDao(){
return new UserDaoImpl();
}
}
AppForInstanceUser
public class AppForInstanceUser {
public static void main(String[] args) {
// 创建实例工厂对象
// UserDaoFactory userDaoFactory = new UserDaoFactory();
// 通过实例工厂对象创造对象
// UserDao userDao = userDaoFactory.getUserDao();
// userDao.save();
ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
UserDao userDao = (UserDao) ctx.getBean("userDao");
userDao.save();
}
}
这个方法名是不固定的每次都需要配置,所以改良一下,使用第四种方法。
UserDaoFactoryBean⭐⭐⭐⭐⭐
public class UserDaoFactoryBean implements FactoryBean<UserDao> {
//代替原始实例工厂中创建对象的方法
@Override
public UserDao getObject() throws Exception {
return new UserDaoImpl();
}
@Override
public Class<?> getObjectType() {
return UserDao.class;
}
}
配置文件
<!-- 方式四: 使用FactoryBean实例化bean-->
<bean id="userDao" class="com.le1a.factory.UserDaoFactoryBean"/>
AppForInstanceUser
public class AppForInstanceUser {
public static void main(String[] args) {
ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
UserDao userDao = (UserDao) ctx.getBean("userDao");
userDao.save();
}
}
可以发现通过FactoryBean
实例化的Bean也是单例的
如果要实现非单例的话,在UserDaoFactoryBean
中新增一个方法
@Override
public boolean isSingleton() {
return false;//true为单例,false为非单例
}
Bean的生命周期
- 生命周期: 从创建到消亡的完整过程
- bean生命周期: bean从创建到销毁的整体过程
- bean生命周期的控制: 在bean创建后到销毁前做一些事情
Bean的生命周期控制(一):使用配置
BookDaoImpl
public class BookDaoImpl implements BookDao {
@Override
public void save() {
System.out.println("book dao save ...");
}
//表示bean初始化对应的操作
public void init(){
System.out.println("init ...");
}
//表示bean销毁前对应的操作
public void destory(){
System.out.println("destory ...");
}
}
配置文件
必须得配置了这两个方法,才会被识别为初始化方法和销毁方法
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="bookDao" class="com.le1a.dao.impl.BookDaoImpl" init-method="init" destroy-method="destory"/>
<bean id="bookService" class="com.le1a.service.impl.BookServiceImpl">
<property name="bookDao" ref="bookDao"/>
</bean>
</beans>
AppForLifeCycle
public class AppForLifeCycle {
public static void main(String[] args) {
ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
BookDao bookDao = (BookDao) ctx.getBean("bookDao");
bookDao.save();
}
}
BookServiceImpl
public class BookServiceImpl implements BookService {
private BookDao bookDao;
@Override
public void save() {
System.out.println("book service ...");
}
public void setBookDao(BookDao bookDao) {
this.bookDao = bookDao;
}
}
在bean中实现初始化和销毁的方法,并且在配置文件中配置,然后就可以实现bean生命周期的控制。
执行AppForLifeCycle
发现初始化方法执行了,但销毁方法并没有被执行
原因是因为现在这个AppForLifeCycle
程序是运行在java虚拟机中的,虚拟机启动了,IoC容器加载配置也启动了,然后把Bean初始化了并且拿到了Bean,下一步程序运行完了,虚拟机就退出了,并没有给Bean销毁的机会。解决办法有如下两种:
Bean销毁方法(一):
使用close()
方法在虚拟机退出之前把容器给关闭
但是发现ApplicationContext
接口并没有实现close()
方法,在ClassPathXmlApplicationContext
接口的父类AbstractApplicationContext.class
中实现了close()
方法,所以只需要把ApplicationContext
改为ClassPathXmlApplicationContext
就可以了
如果想要在程序中正常的关闭容器,使用ClassPathXmlApplicationContext
类就可以关了
Bean销毁方法(二):
使用registerShutdownHook()
设置关闭钩子,表示在关闭虚拟机之前,先把容器关掉!
两种方式的区别:
- close()方法相对暴力,如果close()放在前面,则会导致异常;而
registerShutdownHook()
则可以放在任意位置
Bean的生命周期控制(二): 接口控制
如果bean的初始化和销操作都需要在配置文件中声明对应的方法名的话,太过于复杂。所以来看看Spring的方法来初始化
BookServiceImpl
public class BookServiceImpl implements BookService , InitializingBean, DisposableBean {
private BookDao bookDao;
@Override
public void save() {
System.out.println("book service ...");
}
public void setBookDao(BookDao bookDao) {
this.bookDao = bookDao;
}
@Override
public void destroy() throws Exception {//销毁方法
System.out.println("service destroy");
}
@Override
public void afterPropertiesSet() throws Exception {//在Bean初始化属性设置完之后,执行service的初始化方法
System.out.println("service init");
}
}
Bean同样可以继承这两个接口,并实现初始化和销毁的方法,这样就可以不用在spring配置文件中添加init-method="init" destroy-method="destroy"
BookDaoImpl
public class BookDaoImpl implements BookDao , InitializingBean, DisposableBean {
@Override
public void save() {
System.out.println("book dao save ...");
}
@Override
public void destroy() throws Exception {
System.out.println("bean destroy ...");
}
@Override
public void afterPropertiesSet() throws Exception {
System.out.println("bean init ....");
}
总结:
- 初始化容器
- 创建对象(内存分配)
- 执行构造方法
- 执行属性注入(set操作)
- 执行bean初始化方法
- 使用bean
- 执行业务操作
- 关闭/销毁容器
- 执行bean销毁方法
依赖注入方式
- 思考:向一个类中传递数据的方式有几种?
- 普通方法(set方法)
- 构造方法
- 思考:依赖注入描述了容器中建立bean与bean之间依赖关系的过程,如果bean运行需要的是数字或字符串呢?
- 引用类型
- 简单类型(基本数据类型和String)
- 依赖注入方式
- setter注入
- 简单类型
- 引用类型
- 构造器注入
- 简单类型
- 引用类型
- setter注入
setter注入——引用类型
- 在bean中定义引用类型属性并提供可访问的set方法
public class BookServiceImpl implements BookService , InitializingBean, DisposableBean {
private BookDao bookDao;
public void setBookDao(BookDao bookDao) {
this.bookDao = bookDao;
}
- 配置中使用
property
标签ref
属性注入引用类型对象
<bean id="bookDao" class="com.le1a.dao.impl.BookDaoImpl"/>
<bean id="bookService" class="com.le1a.service.impl.BookServiceImpl">
<property name="bookDao" ref="bookDao"/>
</bean>
如果有多个bean的时候,同样的配置就好了
AppForDISet
public class AppForDISet {
public static void main(String[] args) {
ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
BookService bookService = (BookService) ctx.getBean("bookService");
bookService.save();
}
}
配置文件
<bean id="bookDao" class="com.le1a.dao.impl.BookDaoImpl"/>
<bean id="userDao" class="com.le1a.dao.impl.UserDaoImpl"/>
<bean id="bookService" class="com.le1a.service.impl.BookServiceImpl">
<property name="bookDao" ref="bookDao"/>
<property name="userDao" ref="userDao"/>
</bean>
BookServiceImpl
public class BookServiceImpl implements BookService {
private BookDao bookDao;
private UserDao userDao;
public void setUserDao(UserDao userDao) {
this.userDao = userDao;
}
public void setBookDao(BookDao bookDao) {
this.bookDao = bookDao;
}
@Override
public void save() {
System.out.println("book service ...");
bookDao.save();
userDao.save();
}
setter注入——简单类型
简单类型就直接在Bean里面配置set方法,并且在配置文件中配置bean的property
标签即可
运行结果如图
构造器注入——引用类型
构造器注入的话就把之前BookServiceImpl
的set
方法改为构造方法就好了
public BookServiceImpl(BookDao bookDao, UserDao userDao) {
this.bookDao = bookDao;
this.userDao = userDao;
}
<bean id="bookDao" class="com.le1a.dao.impl.BookDaoImpl"/>
<bean id="userDao" class="com.le1a.dao.impl.UserDaoImpl"/>
<bean id="bookService" class="com.le1a.service.impl.BookServiceImpl">
<constructor-arg name="bookDao" ref="bookDao"/>
<constructor-arg name="userDao" ref="userDao"/>
</bean>
构造器注入——简单类型
BookDaoImpl 在Bean中使用构造器
public class BookDaoImpl implements BookDao{
private int connectionNum;
private String databaseName;
public BookDaoImpl(int connectionNum, String databaseName) {
this.connectionNum = connectionNum;
this.databaseName = databaseName;
}
public void save() {
System.out.println("book dao save ..."+databaseName+","+connectionNum);
}
}
配置文件中配置constructor-arg
标签
<bean id="bookDao" class="com.le1a.dao.impl.BookDaoImpl">
<constructor-arg name="databaseName" value="mysql"/>
<constructor-arg name="connectionNum" value="666"/>
</bean>
高耦合解决办法
这里会出现一个耦合度高的问题,因为配置文件中的name属性是指向的set方法/构造器
的形参,如果形参发生改变,那么这里的name属性同样需要改变,比较麻烦。
<!-- 标准书写
<bean id="bookDao" class="com.le1a.dao.impl.BookDaoImpl">
<constructor-arg name="databaseName" value="mysql"/>
<constructor-arg name="connectionNum" value="666"/>
</bean>
<bean id="userDao" class="com.le1a.dao.impl.UserDaoImpl"/>
<bean id="bookService" class="com.le1a.service.impl.BookServiceImpl">
<constructor-arg name="bookDao" ref="bookDao"/>
<constructor-arg name="userDao" ref="userDao"/>
</bean>
-->
方法一
解决方案就是 不写name属性,写type类型,通过type类型来限制赋值,通过实验发现确实可以
<!-- 解决形参名称问题,与形参名不耦合
<bean id="bookDao" class="com.le1a.dao.impl.BookDaoImpl">
<constructor-arg type="java.lang.String" value="666"/>
<constructor-arg type="java.lang.String" value="mysql"/>
</bean>
<bean id="userDao" class="com.le1a.dao.impl.UserDaoImpl"/>
<bean id="bookService" class="com.le1a.service.impl.BookServiceImpl">
<constructor-arg name="bookDao" ref="bookDao"/>
<constructor-arg name="userDao" ref="userDao"/>
</bean>
-->
但是如果两个参数是同样的类型,就会按照顺序赋值,这显然不是我们想要的
方法二
使用参数位置来解决参数匹配问题
<bean id="bookDao" class="com.le1a.dao.impl.BookDaoImpl">
<constructor-arg index="0" value="666"/>
<constructor-arg index="1" value="mysql"/>
</bean>
<bean id="userDao" class="com.le1a.dao.impl.UserDaoImpl"/>
<bean id="bookService" class="com.le1a.service.impl.BookServiceImpl">
<constructor-arg name="bookDao" ref="bookDao"/>
<constructor-arg name="userDao" ref="userDao"/>
</bean>
依赖注入方式选择
- 强制依赖使用构造器,使用setter注入有概率不进行注入导致null对象出现
- 可选依赖使用setter注入进行,灵活性强
- Spring框架倡导使用构造器,第三方框架内部大多数采用构造器注入的形式对数据初始化,相对严谨
- 如果有必要可以两者同时使用,使用构造器注入完成强制依赖的注入,使用setter注入完成可选依赖的注入
- 实际开发过程中还要根据实际情况分析,如果受控对象没有提供setter方法就必须使用构造器注入
- 自己开发的模块推荐使用setter注入